/*
 * This module is build on top of on J.Bradshaw's vmcController
 * Implements audio media library functions
 * 
 * Copyright (c) 2009 Anthony Jones
 * 
 * Portions copyright (c) 2007 Jonathan Bradshaw
 * 
 * This software code module is provided 'as-is', without any express or implied warranty. 
 * In no event will the authors be held liable for any damages arising from the use of this software.
 * Permission is granted to anyone to use this software for any purpose, including commercial 
 * applications, and to alter it and redistribute it freely.
 * 
 * History:
 * 2009-8-1  Anthony Jones: Created
 * 2009-8-24 Anthony Jones: Added Threading to generate tag cache
 * 
 */

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Windows.Media.Imaging;
using System.Runtime.Serialization.Formatters.Binary;
using System.Drawing;
using System.Drawing.Drawing2D;
//using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Threading;

//using System.Runtime.CompilerServices;

namespace VmcController.AddIn.Commands {
	/// <summary>
	/// Summary description for MsgBox command.
	/// </summary>
	public class photoCmd : ICommand {
		private static WMPLib.WindowsMediaPlayer Player = null;
		private WMPLib.IWMPMedia media;
		private WMPLib.IWMPPlaylist photo_media_play_list;

		private static Dictionary<string, List<int>> keywords;
		private static int cache_ver = -1;
		private static Dictionary<string, string> m_templates = new Dictionary<string, string>();

		private int page_limit = int.MaxValue;
		private int page_start = 0;
		private int kw_count = 0;
		private DateTime first_date = new DateTime(9999, 12, 31);
		private DateTime last_date = new DateTime(1, 1, 1);

		private string debug_last_action = "none";

		private static Thread tag_thread = null;
		private static bool in_generate_cache = false;
		private static bool init_run = false;

		public const int LIST_PHOTOS = 1;
		public const int PLAY_PHOTOS = 2;
		public const int QUEUE_PHOTOS = 3;
		public const int LIST_TAGS = 4;
		public const int CLEAR_CACHE = 5;
		public const int SERV_PHOTO = 6;

		private const string BUILD = ";;BUILD_ID;;";
		private const string ELAPSED = ";;GENERATE_TIME;;";

		private const string CACHE_ID = ";;CACHE_ID;;";
		private const string KEYWORD = ";;KW;;";
		private const string DATE = ";;DT;;";

		private static string CACHE_DIR = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
		                                  "\\VMC_Controller";

		private static string CACHE_FILE = CACHE_DIR + "\\photoCmd.cache";
		private static string CACHE_RESIZE_DIR = CACHE_DIR + "\\Resized";

		private const string DEFAULT_RESULT =
			"idx=%idx%\r\n%if-title%  Title=%title%\r\n%endif%  Filename=%filename%\r\n%if-datetaken%  Date Taken=%datetaken%\r\n%endif%%if-camera%  Camera=%camera%\r\n%endif%%if-tags%  Tags=%tags%\r\n%endif%";

		private const string DEFAULT_HEAD = "photos found=%results_count%\r\n";
		private const string DEFAULT_FOOT = "elapsed seconds=%elapsed_time%";

		private const string DEFAULT_REPORT_HEAD = "";
		private const string DEFAULT_REPORT_RESULT = "tag=%tag%";
		private const string DEFAULT_REPORT_FOOT = "tags found=%results_count%\r\nelapsed seconds=%elapsed_time%";

		public int action = 1;

		public photoCmd(int do_what) {
			action = do_what;
			/* Do init on the statics */
			if (!init_run) {
				init_run = true;
				loadTemplate();
				if (Player == null) {
					Player = new WMPLib.WindowsMediaPlayer();
				}
				photo_media_play_list = Player.mediaCollection.getByAttribute("MediaType", "Photo");
				validate_tags_list();
			}
		}

		public void serialize() {
			debug_last_action = "Opening cache file for serialization: " + CACHE_FILE;

			//Create dir if needed:
			if (!Directory.Exists(CACHE_DIR)) {
				try {
					Directory.CreateDirectory(CACHE_DIR);
				}
				catch (Exception) {
					return;
				}
			}

			Stream stream = File.Open(CACHE_FILE, FileMode.Create, FileAccess.ReadWrite);
			BinaryFormatter formatter = new BinaryFormatter();

			debug_last_action = "Writing serialization to " + CACHE_FILE;
			formatter.Serialize(stream, keywords);
			stream.Close();

			debug_last_action = "Done with serialization";
		}

		public bool deserialize() {
			debug_last_action = "Checking if file exists: " + CACHE_FILE;
			FileInfo fi = new FileInfo(CACHE_FILE);
			if (!fi.Exists) {
				return false;
			}

			debug_last_action = "Opening cache file for deserialization: " + CACHE_FILE;

			Stream stream = File.Open(CACHE_FILE, FileMode.Open, FileAccess.Read);
			BinaryFormatter formatter = new BinaryFormatter();

			debug_last_action = "Reading cache file for deserialization.";
			keywords = (Dictionary<string, List<int>>) formatter.Deserialize(stream);
			stream.Close();

			debug_last_action = "Checking version of cache";
			if (keywords.ContainsKey(CACHE_ID)) {
				cache_ver = keywords[CACHE_ID][0];
			}
			else {
				cache_ver = -1;
			}
			if (keywords.ContainsKey(BUILD)) {
				if (keywords[BUILD][0] != System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Build) {
					cache_ver = -1;
				}
			}
			else {
				cache_ver = -1;
			}

			return true;
		}

		private bool loadTemplate() {
			bool ret = true;

			try {
				Regex re = new Regex("(?<lable>.+?)\t+(?<format>.*$?)");
				StreamReader fTemplate = File.OpenText("photo.template");
				string sIn = null;
				while ((sIn = fTemplate.ReadLine()) != null) {
					Match match = re.Match(sIn);
					if (match.Success) {
						m_templates.Add(match.Groups["lable"].Value, match.Groups["format"].Value);
					}
				}
				fTemplate.Close();
			}
			catch {
				ret = false;
			}

			return ret;
		}

		private string fix_escapes(string s) {
			s = s.Replace("\r", "\\r");
			s = s.Replace("\n", "\\n");
			s = s.Replace("\t", "\\t");

			return s;
		}

		private string getTemplate(string template, string default_template) {
			string tmp = "";

			if (!m_templates.ContainsKey(template)) {
				return default_template;
			}

			tmp = m_templates[template];
			tmp = tmp.Replace("\\r", "\r");
			tmp = tmp.Replace("\\n", "\n");
			tmp = tmp.Replace("\\t", "\t");

			return tmp;
		}

		#region ICommand Members

		/// <summary>
		/// Shows the syntax.
		/// </summary>
		/// <returns></returns>
		public string ShowSyntax() {
			string s = "";
			switch (action) {
				case CLEAR_CACHE:
					s = "[-help] - Forces the index cache to reset (otherwise only resets when the number of photos changes)";
					break;
				case LIST_TAGS:
					s = "[-help] [template:<name>] - Lists all the tags found in the photo collection";
					break;
				case LIST_PHOTOS:
					s =
						"[-help] [is-tagged:tag1;...] [not-tagged:tag1;...] [start-date:m-d-yyyy] [end-date:m-d-yyyy] [template:<name>] ... - Lists all the tags found in the photo collection";
					break;
				case SERV_PHOTO:
					s =
						"[size-x:width] [size-y:height] [ids:index] - Serves the photo to a web browser (all filters apply, but only first matching image is returned)";
					break;
				case PLAY_PHOTOS:
				case QUEUE_PHOTOS:
				default:
					s =
						"[-help] [ids:index1,index2,...] [is-tagged:tag1;tag2...] [not-tagged:tag1;tag2...] [start-date:m-d-yyyy] [end-date:m-d-yyyy] - list / play from photo collection";
					break;
			}

			return s;
		}

		public OpResult showHelp(OpResult or) {
			or.AppendFormat("photo-list [<Parameter List>] - lists all matching photos");
			or.AppendFormat("photo-play [<Parameter List>] - plays all matching photos");
			or.AppendFormat("photo-queue [<Parameter List>] - adds all match photos to the current slideshow");
			or.AppendFormat("photo-serv [ids:<indexlist>] [size-x:width] [size-y:height] - serves (via http) the first image");
			or.AppendFormat("photo-tag-list - lists all tags from all photos");
			or.AppendFormat(
				"photo-clear-list-cache - forces the cache to reset (otherwise only resets when the number of photos changes)");
			or.AppendFormat(" ");
			or.AppendFormat(
				"<Parameter List>: [ids:<indexlist>] [is-tagged:<taglist>] [not-tagged:<taglist>] [start-date:m-d-yyyy] [end-date:m-d-yyyy] [page-limit:x] [page-start:x] [template:name");
			or.AppendFormat("     [ids:<indexlist>] - lists / plays / queues the specified images");
			or.AppendFormat("          <indexlist> is a list of indexes (numbers) separated by commas (no spaces)");
			or.AppendFormat("          Note that specifying image indexes overrides all filters");
			or.AppendFormat(
				"     [is-tagged:<taglist>] - filter images by included tags - limits results to images having all specified tags");
			or.AppendFormat(
				"     [not-tagged:<taglist>] - filter images by excluded tags - limits results to images having none of the tags");
			or.AppendFormat(
				"          <taglist> is a list of one or more tags that are used to filter images. Multiple tags should be separated ");
			or.AppendFormat("               with a ';' - no spaces allowed (use '_' in multi-word tags)");
			or.AppendFormat(
				"     [start-date:m-d-yyyy] - filter images by date - limit results to images taken on or after the specified date");
			or.AppendFormat(
				"     [end-date:m-d-yyyy] - filter images by date - limit results to images taken on or before the specified date");
			or.AppendFormat("     [page-limit:<x>] - where <x> is a number - Limits results to the first <x> images");
			or.AppendFormat("     [page-start:<y>] - where <y> is a number - Results start with the <y> matching image");
			or.AppendFormat("     [template:<name>] - where <name> is a custom template in the \"photo.template\" file");
			or.AppendFormat(
				"     [size-x:<width>] - Resizes the served image, where <width> is the max width of the served image");
			or.AppendFormat(
				"     [size-y:<height>] - Resizes the served image, where <height> is the max height of the served image");
			or.AppendFormat(" ");
			or.AppendFormat("Filter Notes:");
			or.AppendFormat("     - Multiple filters can be used");
			or.AppendFormat("     - The 'page-limit' and 'page-start' paramters can be used to page results");
			or.AppendFormat(
				"     - The start-date and end-date filters are based on the 'DateTaken' value in the image's metadata");
			or.AppendFormat(" ");
			or.AppendFormat("Resizeing Notes:");
			or.AppendFormat("     - Both size-x and size-y must be > 0 or the original image will be returned without resizing.");
			or.AppendFormat(" ");
			or.AppendFormat("Custom Formatting Notes:");
			or.AppendFormat("     All custom formats must be defined in the \"photo.template\" file");
			or.AppendFormat(
				"     The \"photo.template\" file must be manually coppied to the ehome directory (usually C:\\Windows\\ehome)");
			or.AppendFormat("     The \"photo.template\" file contains notes / examples on formatting");

			return or;
		}

		public void validate_tags_list() {
			debug_last_action = "Validating cache...";
			if (cache_ver != photo_media_play_list.count) {
				deserialize();
			}
			if (cache_ver != photo_media_play_list.count) {
				launch_generate_tags_list();
			}
			return;
		}

		public OpResult launch_generate_tags_list() {
			OpResult op_return = new OpResult(OpStatusCode.Ok);

			if (in_generate_cache) {
				if (keywords.ContainsKey(ELAPSED)) {
					op_return.AppendFormat("Already generating cache, last time this operation took {0} seconds.", keywords[ELAPSED][0]);
				}
				else {
					op_return.AppendFormat("Already generating cache, First time generating - be patient.");
				}
				return op_return;
			}
			else {
				tag_thread = new Thread(new ThreadStart(generate_tags_list));
				if (keywords != null && keywords.ContainsKey(ELAPSED)) {
					op_return.AppendFormat("Generating cache, last time this operation took {0} seconds.", keywords[ELAPSED][0]);
				}
				else {
					keywords = new Dictionary<string, List<int>>();
					string tmp_keyword = KEYWORD + "--Please_be_patient_generating_tags_list--";
					keywords.Add(tmp_keyword, new List<int>());
					op_return.AppendFormat("Generating cache, First time generating - be patient.");
				}
				tag_thread.Start();
			}

			return op_return;
		}

		public void generate_tags_list() {
			in_generate_cache = true;
			DateTime startTime = DateTime.Now;

			BitmapFrame bf = null;
			BitmapMetadata bmd = null;

			in_generate_cache = true;

			debug_last_action = "Start generating dictionary";

			//Clear keywords:
			Dictionary<string, List<int>> tmp_keywords = new Dictionary<string, List<int>>();

			for (int x = 0; x < photo_media_play_list.count; x++) {
				try {
					media = photo_media_play_list.get_Item(x);
					debug_last_action = "Generate: Opening image to get metadata for index " + x;
					bf = BitmapFrame.Create(new Uri(media.getItemInfo("SourceURL"), UriKind.Relative));
					if (bf != null) {
						bmd = (BitmapMetadata) bf.Metadata;
						if (bmd != null) {
							debug_last_action = "Generate: Looking at metadata for image: " + media.getItemInfo("SourceURL");
							if (bmd.Keywords != null) {
								for (int y = 0; y < bmd.Keywords.Count; y++) {
									string keyword = KEYWORD + bmd.Keywords[y].Trim();
									keyword = keyword.Replace(' ', '_');
									if (!tmp_keywords.ContainsKey(keyword)) {
										tmp_keywords.Add(keyword, new List<int>());
										kw_count++;
									}
									tmp_keywords[keyword].Add(x);
								}
							}
							if (bmd.DateTaken != null && bmd.DateTaken.Length > 0) {
								string date = DATE + bmd.DateTaken.Split(' ')[0].Trim();
								date = date.Replace('/', '-');
								if (!tmp_keywords.ContainsKey(date)) {
									tmp_keywords.Add(date, new List<int>());
								}
								tmp_keywords[date].Add(x);
							}
						}
					}
				}
				catch (Exception) {
					;
				}
			}
			//Add "CACHE_ID" into Dictionary
			tmp_keywords.Add(CACHE_ID, new List<int>());
			tmp_keywords[CACHE_ID].Add(photo_media_play_list.count);
			cache_ver = photo_media_play_list.count;
			//Add "BUILD" into Dictionary
			tmp_keywords.Add(BUILD, new List<int>());
			tmp_keywords[BUILD].Add(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Build);
			//Add expected generate time "ELAPSED" into Dictionary
			TimeSpan duration = DateTime.Now - startTime;

			tmp_keywords.Add(ELAPSED, new List<int>());
			tmp_keywords[ELAPSED].Add((int) duration.TotalSeconds);

			keywords = tmp_keywords;
			serialize();

			in_generate_cache = false;

			debug_last_action = "Generate: Done";

			return;
		}

		public OpResult list_tags(OpResult opResult, string template) {
			debug_last_action = "List tags: Start";
			validate_tags_list();
			//Sort keywords:
			List<string> keyword_list = new List<string>(keywords.Keys);
			keyword_list.Sort();
			kw_count = 0;
			string output_template = getTemplate(template, DEFAULT_REPORT_RESULT);
			output_template = file_includer(output_template);

			DateTime tmp_date = new DateTime();

			// Header (note no substitutions)
			string s_head = getTemplate(template + "+", DEFAULT_REPORT_HEAD);
			s_head = file_includer(s_head);
			opResult.AppendFormat("{0}", s_head);

			foreach (string s in keyword_list) {
				if (s.StartsWith(KEYWORD)) {
					string kw = s.Substring(KEYWORD.Length);
					opResult.AppendFormat("{0}", do_conditional_replace(output_template, "tag", kw));
					kw_count++;
					//foreach (int i in keywords[s]) opResult.AppendFormat("  photo={0}", i);
				}
				else if (s.StartsWith(DATE)) {
					string date = s.Substring(DATE.Length);
					string[] date_elements = date.Split('-');
					if (date_elements.Length >= 3) {
						tmp_date = new DateTime(Int32.Parse(date_elements[2]),
						                        Int32.Parse(date_elements[0]), Int32.Parse(date_elements[1]));
					}
					if (tmp_date < first_date) {
						first_date = tmp_date;
					}
					if (tmp_date > last_date) {
						last_date = tmp_date;
					}
				}
			}
			debug_last_action = "List tags: Finished foreach loop";

			debug_last_action = "List tags: Done";

			return opResult;
		}

		public HashSet<int> filter_is_tagged(HashSet<int> results, List<string> tags) {
			debug_last_action = "Filter is_tagged: Start";
			//HashSet<int> results = null;
			foreach (string s in tags) {
				if (s.Length > 0) {
					string kw = KEYWORD + s;
					if (!keywords.ContainsKey(kw)) {
						debug_last_action = "Filter is_tagged: Clearing results - No keyword in dictionary: " + s;
						results.Clear();
						break;
					}
					else {
						results.IntersectWith(keywords[kw]);
					}
				}
				debug_last_action = "Filter is_tagged: Applied keyword: " + s;
			}
			debug_last_action = "Filter is_tagged: End";

			return results;
		}

		public HashSet<int> filter_not_tagged(HashSet<int> results, List<string> tags) {
			debug_last_action = "Filter not_tagged: Start";
			//HashSet<int> results = null;
			foreach (string s in tags) {
				if (s.Length > 0) {
					string kw = KEYWORD + s;
					if (keywords.ContainsKey(kw)) {
						results.ExceptWith(keywords[kw]);
					}
				}
				debug_last_action = "Filter not_tagged: Applied keyword: " + s;
			}
			debug_last_action = "Filter not_tagged: End";

			return results;
		}

		public string do_conditional_replace(string s, string item, string v) {
			debug_last_action = "Conditional replace: Start - item: " + item;

			string value = "";
			try {
				value = v;
			}
			catch (Exception) {
				value = "";
			}

			if (value == null) {
				value = "";
			}
			else {
				value = value.Trim();
			}

			int idx_start = -1;
			int idx_end = -1;
			debug_last_action = "Conditional replace: Checking Conditional - item: " + item;
			while ((idx_start = s.IndexOf("%if-" + item + "%")) >= 0) {
				if (value.Length == 0) {
					if ((idx_end = s.IndexOf("%endif%", idx_start)) >= 0) {
						s = s.Substring(0, idx_start) + s.Substring(idx_end + 7);
					}
					else {
						s = s.Substring(0, idx_start);
					}
				}
				else {
					if ((idx_end = s.IndexOf("%endif%", idx_start)) >= 0) {
						s = s.Substring(0, idx_end) + s.Substring(idx_end + 7);
					}
					s = s.Substring(0, idx_start) + s.Substring(idx_start + ("%if-" + item + "%").Length);
				}
			}
			debug_last_action = "Conditional replace: Doing replace - item: " + item;
			s = s.Replace("%" + item + "%", value);

			debug_last_action = "Conditional replace: End - item: " + item;

			return s;
		}

		public string file_includer(string s) {
			int idx_start = -1;
			int idx_end = -1;
			string fn = null;
			while ((idx_start = s.IndexOf("%file-include%")) >= 0) {
				if ((idx_end = s.IndexOf("%endfile%", idx_start)) >= 0) {
					fn = s.Substring((idx_start + ("%file-include%".Length)), (idx_end - (idx_start + ("%file-include%".Length))));
				}
				else {
					fn = s.Substring(idx_start + "%file-include%".Length);
				}
				fn = fix_escapes(fn);

				string file_content = null;
				FileInfo fi = new FileInfo(fn);
				if (!fi.Exists) {
					file_content = "";
				}
				else {
					StreamReader include_stream = File.OpenText(fn);
					file_content = include_stream.ReadToEnd();
					include_stream.Close();
				}
				s = s.Substring(0, idx_start) + file_content + s.Substring(idx_end + "%endfile%".Length);
			}
			return s;
		}

		public string replacer(string s, WMPLib.IWMPMedia photo, int idx) {
			BitmapFrame bf = null;
			BitmapMetadata bmd = null;
			string data = "";

			debug_last_action = "Replacer: Start";

			bf = BitmapFrame.Create(new Uri(photo.getItemInfo("SourceURL"), UriKind.Relative));
			if (bf != null) {
				bmd = (BitmapMetadata) bf.Metadata;
			}

			if (s.IndexOf("%") < 0) {
				return s;
			}

			// Index of photo
			s = s.Replace("%idx%", String.Format("{0}", idx));

			// photo media item properties
			debug_last_action = "Replacer: filename";
			try {
				data = photo.getItemInfo("SourceURL");
			}
			catch (Exception) {
				data = "";
			}
			s = do_conditional_replace(s, "filename", data);

			debug_last_action = "Replacer: url";
			try {
				data = photo.getItemInfo("SourceURL");
			}
			catch (Exception) {
				data = "";
			}
			s = do_conditional_replace(s, "url", data);

			debug_last_action = "Replacer: width";
			try {
				data = String.Format("{0}", photo.imageSourceWidth);
			}
			catch (Exception) {
				data = "";
			}
			s = do_conditional_replace(s, "width", data);

			debug_last_action = "Replacer: height";
			try {
				data = String.Format("{0}", photo.imageSourceHeight);
			}
			catch (Exception) {
				data = "";
			}
			s = do_conditional_replace(s, "height", data);

			debug_last_action = "Replacer: author";
			try {
				data = photo.getItemInfo("Author");
			}
			catch (Exception) {
				data = "";
			}
			s = do_conditional_replace(s, "author", data);

			debug_last_action = "Replacer: filesize";
			try {
				data = photo.getItemInfo("FileSize");
			}
			catch (Exception) {
				data = "";
			}
			s = do_conditional_replace(s, "filesize", data);

			// photo media metadata properties
			if (bmd != null) {
				//tags:
				string tags = "";
				try {
					debug_last_action = "Replacer: Getting tags";
					if (bmd.Keywords != null) {
						tags = bmd.Keywords[0].Trim();
						for (int y = 1; y < bmd.Keywords.Count; y++) tags += ";" + bmd.Keywords[y].Trim();
					}
					tags = tags.Replace(' ', '_');
				}
				catch (Exception) {
					tags = "";
				}
				debug_last_action = "Replacer: tags";
				s = do_conditional_replace(s, "tags", tags);

				debug_last_action = "Replacer: title";
				try {
					data = bmd.Title;
				}
				catch (Exception) {
					data = "";
				}
				s = do_conditional_replace(s, "title", data);

				debug_last_action = "Replacer: subject";
				try {
					data = bmd.Subject;
				}
				catch (Exception) {
					data = "";
				}
				s = do_conditional_replace(s, "subject", data);

				debug_last_action = "Replacer: location";
				try {
					data = bmd.Location;
				}
				catch (Exception) {
					data = "";
				}
				s = do_conditional_replace(s, "location", data);

				debug_last_action = "Replacer: datetaken";
				try {
					data = bmd.DateTaken;
				}
				catch (Exception) {
					data = "";
				}
				s = do_conditional_replace(s, "datetaken", data);

				debug_last_action = "Replacer: copyright";
				try {
					data = bmd.Copyright;
				}
				catch (Exception) {
					data = "";
				}
				s = do_conditional_replace(s, "copyright", data);

				debug_last_action = "Replacer: comment";
				try {
					data = bmd.Comment;
				}
				catch (Exception) {
					data = "";
				}
				s = do_conditional_replace(s, "comment", data);

				debug_last_action = "Replacer: camera";
				try {
					data = bmd.CameraManufacturer + " " + bmd.CameraModel;
				}
				catch (Exception) {
					data = "";
				}
				s = do_conditional_replace(s, "camera", data);

				debug_last_action = "Replacer: rating";
				try {
					data = String.Format("{0}", bmd.Rating);
				}
				catch (Exception) {
					data = "";
				}
				s = do_conditional_replace(s, "rating", data);
			}
			else {
				debug_last_action = "Replacer: null tags";
				s = do_conditional_replace(s, "tags", "");
				debug_last_action = "Replacer: null title";
				s = do_conditional_replace(s, "title", "");
				debug_last_action = "Replacer: null subject";
				s = do_conditional_replace(s, "subject", "");
				debug_last_action = "Replacer: null location";
				s = do_conditional_replace(s, "location", "");
				debug_last_action = "Replacer: null datetaken";
				s = do_conditional_replace(s, "datetaken", "");
				debug_last_action = "Replacer: null copyright";
				s = do_conditional_replace(s, "copyright", "");
				debug_last_action = "Replacer: null comment";
				s = do_conditional_replace(s, "comment", "");
				debug_last_action = "Replacer: null camera";
				s = do_conditional_replace(s, "camera", "");
				debug_last_action = "Replacer: null rating";
				s = do_conditional_replace(s, "rating", "");
			}
			debug_last_action = "Replacer: End";

			return s;
		}

		private int parse_int(string s) {
			int ret_val = 0;
			int idx = 0;

			while (idx < s.Length && s[idx] <= '9' && s[idx] >= '0')
				ret_val = (10*ret_val) + (s[idx++] - '0');

			return ret_val;
		}

		private HashSet<int> parse_ids(string s) {
			HashSet<int> ret_set = new HashSet<int>();
			string s_id = s.Split(' ')[0];
			string[] id_list = s.Split(',');

			foreach (string id in id_list) ret_set.Add(parse_int(id));

			return ret_set;
		}

		public OpResult getPhoto(WMPLib.IWMPMedia photo, int r_w, int r_h) {
			string filename = photo.getItemInfo("SourceURL");
			string filetype = "";
			try {
				filetype = photo.getItemInfo("FileType");
			}
			catch (Exception) {
				filetype = "jpeg";
			}
			return getPhoto(filename, filetype, r_w, r_h);
		}

		public OpResult getPhoto(string filename, string filetype, int r_w, int r_h) {
			OpResult opResult = new OpResult(OpStatusCode.OkImage);
			if (r_w > 0 && r_h > 0) {
				filename = resize(filename, r_w, r_h);
			}
			FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);

			// Create a reader that can read bytes from the FileStream.
			BinaryReader reader = new BinaryReader(fs);
			byte[] bytes = new byte[fs.Length];
			int read;
			string sResponse = "";
			while ((read = reader.Read(bytes, 0, bytes.Length)) != 0) {
				// Read from the file and write the data to the network
				sResponse = sResponse + Encoding.ASCII.GetString(bytes, 0, read);
			}
			reader.Close();
			fs.Close();

			opResult.ContentText = Convert.ToBase64String(bytes);
			return opResult;
		}

		private static Image resizeImage(Image original, int w, int h) {
			int original_w = original.Width;
			int original_h = original.Height;

			float percent = 0;
			float percent_w = 0;
			float percent_h = 0;

			percent_w = ((float) w/(float) original_w);
			percent_h = ((float) h/(float) original_h);

			if (percent_h < percent_w) {
				percent = percent_h;
			}
			else {
				percent = percent_w;
			}

			int destWidth = (int) (original_w*percent);
			int destHeight = (int) (original_h*percent);

			Bitmap b = new Bitmap(destWidth, destHeight);
			Graphics g = Graphics.FromImage((Image) b);
			g.InterpolationMode = InterpolationMode.HighQualityBicubic;

			g.DrawImage(original, 0, 0, destWidth, destHeight);
			g.Dispose();

			return (Image) b;
		}

		private ImageCodecInfo getEncoderInfo(string mimeType) {
			// Get image codecs for all image formats
			ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();

			// Find the correct image codec
			for (int i = 0; i < codecs.Length; i++)
				if (codecs[i].MimeType == mimeType) {
					return codecs[i];
				}
			return null;
		}

		private void saveJpeg(string path, Bitmap img, long quality) {
			// Encoder parameter for image quality
			EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);

			// Jpeg image codec
			ImageCodecInfo jpegCodec = this.getEncoderInfo("image/jpeg");

			if (jpegCodec == null) {
				return;
			}

			EncoderParameters encoderParams = new EncoderParameters(1);
			encoderParams.Param[0] = qualityParam;

			img.Save(path, jpegCodec, encoderParams);
		}

		public string resize(string fn, int max_w, int max_h) {
			string cached_file = fn;

			try {
				//Create dir if needed:
				if (!Directory.Exists(CACHE_RESIZE_DIR)) {
					try {
						Directory.CreateDirectory(CACHE_RESIZE_DIR);
					}
					catch (Exception) {
						return "";
					}
				}

				cached_file = cached_file.Replace("\\", "_") + String.Format("_{0}_{1}", max_w, max_h);
				cached_file = cached_file.Replace(":", "_") + String.Format("_{0}_{1}", max_w, max_h);

				cached_file = CACHE_RESIZE_DIR + "\\" + cached_file;

				FileInfo fi = new FileInfo(cached_file);
				if (!fi.Exists) {
					Image img = Image.FromFile(fn);
					img = resizeImage(img, max_w, max_h);
					saveJpeg(cached_file, new Bitmap(img), 85L);
				}
			}
			catch (Exception) {
				cached_file = fn;
			}
			return cached_file;
		}

		/// <summary>
		/// Executes the specified param.
		/// </summary>
		/// <param name="param">The param.</param>
		/// <param name="result">The result.</param>
		/// <returns></returns>
		public OpResult Execute(string param) {
			debug_last_action = "Execute: Start";

			HashSet<int> results = new HashSet<int>();

			DateTime startTime = DateTime.Now;

			OpResult opResult = new OpResult();
			opResult.StatusCode = OpStatusCode.Ok;

			string template = "";

			page_limit = 0xFFFF;
			page_start = 0;

			try {
				if (param.IndexOf("-help") >= 0) {
					opResult = showHelp(opResult);
					return opResult;
				}
				if (Player == null) {
					Player = new WMPLib.WindowsMediaPlayer();
				}
				photo_media_play_list = Player.mediaCollection.getByAttribute("MediaType", "Photo");

				debug_last_action = "Execution: Parsing params";
				// "Paging"?
				if (param.Contains("page-limit:")) {
					page_limit = parse_int(param.Substring(param.IndexOf("page-limit:") + "page-limit:".Length));
				}
				if (param.Contains("page-start:")) {
					page_start = parse_int(param.Substring(param.IndexOf("page-start:") + "page-start:".Length));
				}

				// Results specified?
				if (param.Contains("ids:")) {
					results = parse_ids(param.Substring(param.IndexOf("ids:") + "ids:".Length));
				}

				// Use Custom Template?
				if (param.Contains("template:")) {
					template = param.Substring(param.IndexOf("template:") + "template:".Length);
					if (template.IndexOf(" ") >= 0) {
						template = template.Substring(0, template.IndexOf(" "));
					}
				}

				switch (action) {
					case CLEAR_CACHE:
						opResult = launch_generate_tags_list();
						break;
					case LIST_TAGS:
						opResult = list_tags(opResult, template);
						break;
					case LIST_PHOTOS:
					case PLAY_PHOTOS:
					case QUEUE_PHOTOS:
					case SERV_PHOTO:
						if (results.Count == 0) {
							validate_tags_list();
							// Date Limited?
							if (param.Contains("start-date:") || param.Contains("end-date:")) {
								DateTime start_date = new DateTime(1, 1, 1);
								DateTime end_date = new DateTime(9999, 12, 31);
								DateTime tmp_date = new DateTime();
								if (param.Contains("start-date:")) {
									string date_String = param.Substring(param.IndexOf("start-date:") + "start-date:".Length);
									if (date_String.IndexOf(" ") >= 0) {
										date_String = date_String.Substring(0, date_String.IndexOf(" "));
									}
									string[] date_elements = date_String.Split('-');
									if (date_elements.Length >= 3) {
										start_date = new DateTime(Int32.Parse(date_elements[2]),
										                          Int32.Parse(date_elements[0]), Int32.Parse(date_elements[1]));
									}
								}
								if (param.Contains("end-date:")) {
									string date_String = param.Substring(param.IndexOf("end-date:") + "end-date:".Length);
									if (date_String.IndexOf(" ") >= 0) {
										date_String = date_String.Substring(0, date_String.IndexOf(" "));
									}
									string[] date_elements = date_String.Split('-');
									if (date_elements.Length >= 3) {
										end_date = new DateTime(Int32.Parse(date_elements[2]),
										                        Int32.Parse(date_elements[0]), Int32.Parse(date_elements[1]));
									}
								}

								foreach (string s in keywords.Keys) {
									if (s.StartsWith(DATE)) {
										string date = s.Substring(DATE.Length);
										string[] date_elements = date.Split('-');
										if (date_elements.Length >= 3) {
											tmp_date = new DateTime(Int32.Parse(date_elements[2]),
											                        Int32.Parse(date_elements[0]), Int32.Parse(date_elements[1]));
										}
										if (tmp_date >= start_date && tmp_date <= end_date) {
											results.UnionWith(keywords[s]);
										}
									}
								}
							}
							else {
								// Full set of photos into result:
								for (int i = 0; i < photo_media_play_list.count; i++) results.Add(i);
							}
							if (param.Contains("is-tagged:")) {
								string tag_string = param.Substring(param.IndexOf("is-tagged:") + "is-tagged:".Length);
								if (tag_string.IndexOf(" ") >= 0) {
									tag_string = tag_string.Substring(0, tag_string.IndexOf(" "));
								}
								List<string> tag_list = new List<string>(tag_string.Split(';'));
								results = filter_is_tagged(results, tag_list);
							}
							if (param.Contains("not-tagged:")) {
								string tag_string = param.Substring(param.IndexOf("not-tagged:") + "not-tagged:".Length);
								if (tag_string.IndexOf(" ") >= 0) {
									tag_string = tag_string.Substring(0, tag_string.IndexOf(" "));
								}
								List<string> tag_list = new List<string>(tag_string.Split(';'));
								results = filter_not_tagged(results, tag_list);
							}
						}

						// Output results
						if (action == LIST_PHOTOS) {
							string template_result = getTemplate(template, DEFAULT_RESULT);
							template_result = file_includer(template_result);

							debug_last_action = "Execute: List: Header";
							string s_head = getTemplate(template + "+", DEFAULT_HEAD);
							s_head = file_includer(s_head);
							s_head = do_conditional_replace(s_head, "results_count", String.Format("{0}", results.Count));
							opResult.AppendFormat("{0}", s_head);

							debug_last_action = "Execute: List: Items";

							int result_count = 0;
							foreach (int i in results) {
								if (result_count >= page_start && result_count < page_start + page_limit) {
									string s = "";
									s = replacer(template_result, photo_media_play_list.get_Item(i), i);
									opResult.AppendFormat("{0}", s);
								}
								result_count++;
							}
							debug_last_action = "Execute: List: Footer";
							TimeSpan duration = DateTime.Now - startTime;
							string s_foot = getTemplate(template + "-", DEFAULT_FOOT);
							s_foot = file_includer(s_foot);
							s_foot = do_conditional_replace(s_foot, "elapsed_time", String.Format("{0}", duration.TotalSeconds));
							s_foot = do_conditional_replace(s_foot, "results_count", String.Format("{0}", results.Count));
							opResult.AppendFormat("{0}", s_foot);
						}
						else if (action == PLAY_PHOTOS || action == QUEUE_PHOTOS) {
							bool play = false;
							if (action == PLAY_PHOTOS) {
								play = true;
							}
							debug_last_action = "Execute: Play / Queue Start";
							int result_count = 0;
							foreach (int i in results) {
								if (play) {
									play = false;
									// If first photo to play: Clear out existing photos in show directory
									// Delete directory 
									// Create directory
								}
								// Copy photo in to directory
								result_count++;
							}
							if (action == PLAY_PHOTOS) {
								opResult.AppendFormat("Created slide show with {0} photos", result_count);
							}
							else {
								opResult.AppendFormat("Added {0} photos to slide show ", result_count);
							}
							// Start slide show using "show directory" (only if play)?
							//                            Microsoft.MediaCenter.Hosting.AddInHost.Current.MediaCenterEnvironment.NavigateToPage(Microsoft.MediaCenter.PageId.MyPictures, slideshowpath);
							debug_last_action = "Execute: Play / Queue End";
						}
						else if (action == SERV_PHOTO) {
							int size_x = 0;
							int size_y = 0;
							debug_last_action = "Execute: Serv Start";
							if (param.Contains("size-x:")) {
								string tmp_size = param.Substring(param.IndexOf("size-x:") + "size-x:".Length);
								if (tmp_size.IndexOf(" ") >= 0) {
									tmp_size = tmp_size.Substring(0, tmp_size.IndexOf(" "));
								}
								size_x = Convert.ToInt32(tmp_size);
							}
							if (param.Contains("size-y:")) {
								string tmp_size = param.Substring(param.IndexOf("size-y:") + "size-y:".Length);
								if (tmp_size.IndexOf(" ") >= 0) {
									tmp_size = tmp_size.Substring(0, tmp_size.IndexOf(" "));
								}
								size_y = Convert.ToInt32(tmp_size);
							}
							// Return fist photo (may be only one)
							int result_count = 0;
							foreach (int i in results) {
								if (result_count >= page_start && result_count < page_start + page_limit) {
									opResult = getPhoto(photo_media_play_list.get_Item(i), size_x, size_y);
									break;
								}
								result_count++;
							}
							debug_last_action = "Execute: Serv End";
						}

						break;
				}
			}
			catch (Exception ex) {
				opResult = new OpResult();
				opResult.StatusCode = OpStatusCode.Exception;
				opResult.StatusText = ex.Message;
				opResult.AppendFormat("{0}", debug_last_action);
				opResult.AppendFormat("{0}", ex.Message);
			}

			if (action == LIST_TAGS) {
				debug_last_action = "Execute: List: Footer";
				TimeSpan duration = DateTime.Now - startTime;
				string s_foot = getTemplate(template + "-", DEFAULT_REPORT_FOOT);
				s_foot = file_includer(s_foot);
				s_foot = do_conditional_replace(s_foot, "elapsed_time", String.Format("{0}", duration.TotalSeconds));
				s_foot = do_conditional_replace(s_foot, "results_count", String.Format("{0}", kw_count));
				s_foot = do_conditional_replace(s_foot, "first_date", String.Format("{0}", first_date.ToShortDateString()));
				s_foot = do_conditional_replace(s_foot, "last_date", String.Format("{0}", last_date.ToShortDateString()));
				opResult.AppendFormat("{0}", s_foot);
			}

			debug_last_action = "Execute: End";

			return opResult;
		}

		#endregion
	}
}